/**
 * Class to generate an image based the diagram interchange information in a
 * BPMN 2.0 process.
 *
 * @author (Javascript) Dmitry Farafonov
 */

var ProcessDiagramGenerator = {
  options: {},

  processDiagramCanvas: [],

  activityDrawInstructions: {},

  processDiagrams: {},

  diagramBreadCrumbs: null,

  init: function () {
    // start event
    this.activityDrawInstructions['startEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawNoneStartEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // start timer event
    this.activityDrawInstructions['startTimerEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawTimerStartEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        activityImpl.getProperty('name')
      );
    };

    // start event
    this.activityDrawInstructions['messageStartEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawMessageStartEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        activityImpl.getProperty('name')
      );
    };

    // start signal event
    this.activityDrawInstructions['startSignalEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawSignalStartEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        activityImpl.getProperty('name')
      );
    };

    // start multiple event
    this.activityDrawInstructions['startMultipleEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawMultipleStartEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        activityImpl.getProperty('name')
      );
    };

    // signal catch
    this.activityDrawInstructions['intermediateSignalCatch'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingSignalEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message catch
    this.activityDrawInstructions['intermediateMessageCatch'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingMessageEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple catch
    this.activityDrawInstructions['intermediateMultipleCatch'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingMultipleEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // signal throw
    this.activityDrawInstructions['intermediateSignalThrow'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingSignalEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        activityImpl.getProperty('name')
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message throw
    this.activityDrawInstructions['intermediateMessageThrow'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingMessageEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        activityImpl.getProperty('name')
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple throw
    this.activityDrawInstructions['intermediateMultipleThrow'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingMultipleEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        activityImpl.getProperty('name')
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // none throw
    this.activityDrawInstructions['intermediateThrowEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawThrowingNoneEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        activityImpl.getProperty('name')
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // end event
    this.activityDrawInstructions['endEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawNoneEndEvent(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // error end event
    this.activityDrawInstructions['errorEndEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawErrorEndEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // message end event
    this.activityDrawInstructions['messageEndEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawMessageEndEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // signal end event
    this.activityDrawInstructions['signalEndEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawSignalEndEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // multiple end event
    this.activityDrawInstructions['multipleEndEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawMultipleEndEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // terminate end event
    this.activityDrawInstructions['terminateEndEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawTerminateEndEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // error start event
    this.activityDrawInstructions['errorStartEvent'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawErrorStartEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        activityImpl.getProperty('name')
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // task
    this.activityDrawInstructions['task'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      // TODO:
      //console.error("task is not implemented yet");
      /*
			var activityImpl = this;
			processDiagramCanvas.drawTask(activityImpl.getProperty("name"), activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight(), thickBorder);
			*/
    };

    // user task
    this.activityDrawInstructions['userTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawUserTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // script task
    this.activityDrawInstructions['scriptTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawScriptTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // service task
    this.activityDrawInstructions['serviceTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawServiceTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // receive task
    this.activityDrawInstructions['receiveTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawReceiveTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // send task
    this.activityDrawInstructions['sendTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawSendTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // manual task
    this.activityDrawInstructions['manualTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawManualTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // businessRuleTask task
    this.activityDrawInstructions['businessRuleTask'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawBusinessRuleTask(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // exclusive gateway
    this.activityDrawInstructions['exclusiveGateway'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawExclusiveGateway(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // inclusive gateway
    this.activityDrawInstructions['inclusiveGateway'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawInclusiveGateway(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // parallel gateway
    this.activityDrawInstructions['parallelGateway'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawParallelGateway(activityImpl.getX(), activityImpl.getY(), activityImpl.getWidth(), activityImpl.getHeight());
    };

    // eventBasedGateway
    this.activityDrawInstructions['eventBasedGateway'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      processDiagramCanvas.drawEventBasedGateway(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    // Boundary timer
    this.activityDrawInstructions['boundaryTimer'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingTimerEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary catch error
    this.activityDrawInstructions['boundaryError'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingErrorEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary signal event
    this.activityDrawInstructions['boundarySignal'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingSignalEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // Boundary message event
    this.activityDrawInstructions['boundaryMessage'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = activityImpl.getProperty('isInterrupting');
      processDiagramCanvas.drawCatchingMessageEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        null
      );

      var label = ProcessDiagramGenerator.getActivitiLabel(activityImpl);
      if (label) processDiagramCanvas.drawLabel(label.text, label.x, label.y, label.width, label.height);
    };

    // timer catch event
    this.activityDrawInstructions['intermediateTimer'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);

      var isInterrupting = null;
      processDiagramCanvas.drawCatchingTimerEvent(
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight(),
        isInterrupting,
        activityImpl.getProperty('name')
      );
    };

    // subprocess
    this.activityDrawInstructions['subProcess'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      // TODO:

      processDiagramCanvas.setConextObject(activityImpl);

      var isExpanded = activityImpl.getProperty('isExpanded');
      var isTriggeredByEvent = activityImpl.getProperty('triggeredByEvent');
      if (isTriggeredByEvent == undefined) {
        isTriggeredByEvent = true;
      }
      // TODO: check why isTriggeredByEvent = true when undefined
      isTriggeredByEvent = false;

      if (isExpanded != undefined && isExpanded == false) {
        processDiagramCanvas.drawCollapsedSubProcess(
          activityImpl.getProperty('name'),
          activityImpl.getX(),
          activityImpl.getY(),
          activityImpl.getWidth(),
          activityImpl.getHeight(),
          isTriggeredByEvent
        );
      } else {
        processDiagramCanvas.drawExpandedSubProcess(
          activityImpl.getProperty('name'),
          activityImpl.getX(),
          activityImpl.getY(),
          activityImpl.getWidth(),
          activityImpl.getHeight(),
          isTriggeredByEvent
        );
      }

      //console.error("subProcess is not implemented yet");
    };

    // call activity
    this.activityDrawInstructions['callActivity'] = function () {
      var activityImpl = this.activity;
      var processDiagramCanvas = this.processDiagramCanvas;
      processDiagramCanvas.setConextObject(activityImpl);
      processDiagramCanvas.drawCollapsedCallActivity(
        activityImpl.getProperty('name'),
        activityImpl.getX(),
        activityImpl.getY(),
        activityImpl.getWidth(),
        activityImpl.getHeight()
      );
    };

    $(document).ready(function () {
      // Protect right click on SVG elements (and on canvas too)
      document.body.oncontextmenu = function (event) {
        if (
          window.event.srcElement.tagName == 'shape' ||
          (window.event.srcElement.tagName == 'DIV' && window.event.srcElement.parentElement.className == 'diagram')
        ) {
          // IE DIAGRAM CANVAS OR SHAPE DETECTED!
          return false;
        }
        return !Object.isSVGElement(window.event.srcElement);
      };
    });
  },

  getActivitiLabel: function (activityImpl) {
    /*
	     TODO: Label object should be in activityImpl and looks like:
	     {
	       x: 250,
	       y: 250,
	       width: 80,
	       height: 30
	     }
	     And then:
	     if (!activityImpl.label)
	       return null;
	     var label = activityImpl.label;
	     label.text = activityImpl.name;
	     return label;
	   */

    // But now default label for all events is:
    return {
      text: activityImpl.getProperty('name'),
      x: activityImpl.getX() + 0.5 + activityImpl.getWidth() / 2,
      y: activityImpl.getY() + 0.5 + activityImpl.getHeight() + ICON_PADDING,
      width: 100,
      height: 0,
    };
  },

  generateDiagram: function (processDefinitionDiagramLayout) {
    // Init canvas
    var processDefinitionId = processDefinitionDiagramLayout.processDefinition.id;
    //console.log("Init canvas ", processDefinitionId);

    if (this.getProcessDiagram(processDefinitionId) != undefined) {
      // TODO: may be reset canvas if exists.. Or just show
      //console.log("ProcessDiagram '" + processDefinitionId + "' is already generated. Just show it.");
      return;
    }
    var processDiagram = this.initProcessDiagramCanvas(processDefinitionDiagramLayout);
    var processDiagramCanvas = processDiagram.diagramCanvas;

    // Draw pool shape, if process is participant in collaboration

    if (processDefinitionDiagramLayout.participantProcess != undefined) {
      //console.log("Draw pool shape");
      var pProc = processDefinitionDiagramLayout.participantProcess;
      processDiagramCanvas.drawPoolOrLane(pProc.x, pProc.y, pProc.width, pProc.height, pProc.name);
    }

    var laneSets = processDefinitionDiagramLayout.laneSets;
    var activities = processDefinitionDiagramLayout.activities;
    var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;

    pb1.set('value', 0);
    var cnt = 0;
    if (laneSets)
      for (var i in laneSets) {
        cnt += laneSets[i].lanes.length;
      }
    if (activities) cnt += activities.length;
    if (sequenceFlows) cnt += sequenceFlows.length;
    var step = cnt > 0 ? 100 / cnt : 0;
    var progress = 0;
    //console.log("progress bar step: ", step);

    var task1 = new $.AsyncQueue();

    // Draw lanes

    task1.add(function (task1) {
      if (!laneSets) laneSets = [];
      //console.log("> draw lane sets, count:", laneSets.length)
    });

    for (var i in laneSets) {
      var laneSet = laneSets[i];
      //laneSet.id, laneSet.name

      task1.add(laneSet.lanes, function (task1, lane) {
        progress += step;
        pb1.set('value', parseInt(progress));

        //console.log("--> laneId: " + lane.name + ", name: " + lane.name);

        processDiagramCanvas.drawPoolOrLane(lane.x, lane.y, lane.width, lane.height, lane.name);
      });
    }

    // Draw activities

    task1.add(function (task1) {
      if (!activities) activities = [];
      //console.log("> draw activities, count:", activities.length)
    });

    var activitiesLength = activities.length;
    task1.add(activities, function (task1, activityJson) {
      var activity = new ActivityImpl(activityJson);
      activitiesLength--;
      progress += step;
      pb1.set('value', parseInt(progress));
      //console.log(activitiesLength, "--> activityId: " + activity.getId() + ", name: " + activity.getProperty("name"));
      ProcessDiagramGenerator.drawActivity(processDiagramCanvas, activity);
    });

    // Draw sequence-flows

    task1.add(function (task1) {
      if (!sequenceFlows) sequenceFlows = [];
      //console.log("> draw sequence flows, count:", sequenceFlows.length)
    });

    var flowsLength = sequenceFlows.length;
    task1.add(sequenceFlows, function (task1, flow) {
      var waypoints = [];
      for (var j in flow.xPointArray) {
        waypoints[j] = { x: flow.xPointArray[j], y: flow.yPointArray[j] };
      }
      var isDefault = flow.isDefault;
      var isConditional = flow.isConditional;
      var isHighLighted = flow.isHighLighted;

      // TODO: add source and destination for sequence flows in REST
      // parse for test
      var f = flow.flow;
      var matches = f.match(/\((.*)\)--.*-->\((.*)\)/);
      var sourceActivityId, destinationActivityId;
      if (matches != null) {
        sourceActivityId = matches[1];
        destinationActivityId = matches[2];
      }
      flow.sourceActivityId = sourceActivityId;
      flow.destinationActivityId = destinationActivityId;
      //
      flowsLength--;
      progress += step;
      pb1.set('value', parseInt(progress));

      //console.log(flowsLength, "--> flow: " + flow.flow);

      processDiagramCanvas.setConextObject(flow);
      processDiagramCanvas.drawSequenceflow(waypoints, isConditional, isDefault, isHighLighted);
    });

    task1.onComplete(function () {
      if (progress < 100) pb1.set('value', 100);
      //console.log("COMPLETE!!!");

      //console.timeEnd('generateDiagram');
    });

    task1.run();
  },

  getProcessDiagram: function (processDefinitionId) {
    return this.processDiagrams[processDefinitionId];
  },

  initProcessDiagramCanvas: function (processDefinitionDiagramLayout) {
    var minX = 0;
    var maxX = 0;
    var minY = 0;
    var maxY = 0;

    if (processDefinitionDiagramLayout.participantProcess != undefined) {
      var pProc = processDefinitionDiagramLayout.participantProcess;

      minX = pProc.x;
      maxX = pProc.x + pProc.width;
      minY = pProc.y;
      maxY = pProc.y + pProc.height;
    }

    var activities = processDefinitionDiagramLayout.activities;
    for (var i in activities) {
      var activityJson = activities[i];
      var activity = new ActivityImpl(activityJson);

      // width
      if (activity.getX() + activity.getWidth() > maxX) {
        maxX = activity.getX() + activity.getWidth();
      }
      if (activity.getX() < minX) {
        minX = activity.getX();
      }
      // height
      if (activity.getY() + activity.getHeight() > maxY) {
        maxY = activity.getY() + activity.getHeight();
      }
      if (activity.getY() < minY) {
        minY = activity.getY();
      }
    }

    var sequenceFlows = processDefinitionDiagramLayout.sequenceFlows;
    for (var i in sequenceFlows) {
      var flow = sequenceFlows[i];
      var waypoints = [];
      for (var j in flow.xPointArray) {
        waypoints[j] = { x: flow.xPointArray[j], y: flow.yPointArray[j] };

        // width
        if (waypoints[j].x > maxX) {
          maxX = waypoints[j].x;
        }
        if (waypoints[j].x < minX) {
          minX = waypoints[j].x;
        }
        // height
        if (waypoints[j].y > maxY) {
          maxY = waypoints[j].y;
        }
        if (waypoints[j].y < minY) {
          minY = waypoints[j].y;
        }
      }
    }

    var laneSets = processDefinitionDiagramLayout.laneSets;
    for (var i in laneSets) {
      var laneSet = laneSets[i];
      //laneSet.id, laneSet.name

      for (var j in laneSet.lanes) {
        var lane = laneSet.lanes[j];
        // width
        if (lane.x + lane.width > maxX) {
          maxX = lane.x + lane.width;
        }
        if (lane.x < minX) {
          minX = lane.x;
        }
        // height
        if (lane.y + lane.height > maxY) {
          maxY = lane.y + lane.height;
        }
        if (lane.y < minY) {
          minY = lane.y;
        }
      }
    }

    var diagramCanvas = new ProcessDiagramCanvas();
    if (diagramCanvas) {
      // create div in diagramHolder
      var diagramHolder = document.getElementById(this.options.diagramHolderId);
      if (!diagramHolder) throw { msg: 'Diagram holder not found', error: 'diagramHolderNotFound' };
      var div = document.createElement('DIV');
      div.id = processDefinitionDiagramLayout.processDefinition.id;
      div.className = 'diagram';
      diagramHolder.appendChild(div);

      diagramCanvas.init(maxX + 20, maxY + 20, processDefinitionDiagramLayout.processDefinition.id);
      this.processDiagrams[processDefinitionDiagramLayout.processDefinition.id] = {
        processDefinitionDiagramLayout: processDefinitionDiagramLayout,
        diagramCanvas: diagramCanvas,
      };
    }
    return this.getProcessDiagram(processDefinitionDiagramLayout.processDefinition.id);
    //return new DefaultProcessDiagramCanvas(maxX + 10, maxY + 10, minX, minY);
  },

  drawActivity: function (processDiagramCanvas, activity, highLightedActivities) {
    var type = activity.getProperty('type');
    var drawInstruction = this.activityDrawInstructions[type];
    if (drawInstruction != null) {
      drawInstruction.apply({ processDiagramCanvas: processDiagramCanvas, activity: activity });
    } else {
      //console.error("no drawInstruction for " + type + ": ", activity);
    }

    // Actually draw the markers
    if (activity.getProperty('multiInstance') != undefined || activity.getProperty('collapsed') != undefined) {
      //console.log(activity.getProperty("name"), activity.properties);
      var multiInstanceSequential = activity.getProperty('multiInstance') == 'sequential';
      var multiInstanceParallel = activity.getProperty('multiInstance') == 'parrallel';
      var collapsed = activity.getProperty('collapsed');
      processDiagramCanvas.drawActivityMarkers(
        activity.getX(),
        activity.getY(),
        activity.getWidth(),
        activity.getHeight(),
        multiInstanceSequential,
        multiInstanceParallel,
        collapsed
      );
    }
    /*
		processDiagramCanvas.drawActivityMarkers(activity.getX(), activity.getY(), activity.getWidth(), activity.getHeight(), multiInstanceSequential,
              multiInstanceParallel, collapsed);
		*/

    // TODO: Draw highlighted activities if they are present
  },

  setHighLights: function (highLights) {
    if (highLights.processDefinitionId == undefined) {
      //console.error("Process instance " + highLights.processInstanceId + " doesn't exist");
      return;
    }

    var processDiagram = this.getProcessDiagram(highLights.processDefinitionId);
    if (processDiagram == undefined) {
      //console.error("Process diagram " + highLights.processDefinitionId + " not found");
      return;
    }

    var processDiagramCanvas = processDiagram.diagramCanvas;

    // TODO: remove highLightes from all activities before set new highLight
    for (var i in highLights.activities) {
      var activityId = highLights.activities[i];
      processDiagramCanvas.highLightActivity(activityId);
    }

    // TODO: remove highLightes from all flows before set new highLight
    for (var i in highLights.flows) {
      var flowId = highLights.flows[i];
      var object = processDiagramCanvas.g.getById(flowId);
      var flow = object.data('contextObject');
      flow.isHighLighted = true;
      processDiagramCanvas.highLightFlow(flowId);
    }
  },

  drawHighLights: function (processInstanceId) {
    // Load highLights for the processInstanceId
    /*
		var url = Lang.sub(this.options.processInstanceHighLightsUrl, {processInstanceId: processInstanceId});
		$.ajax({
			url: url,
			type: 'GET',
			dataType: 'json',
			cache: false,
			async: true,
		}).done(function(data) {
			var highLights = data;
			if (!highLights) {
				console.log("highLights not found");
				return;
			}
			
			console.log("highLights[" + highLights.processDefinitionId + "][" + processInstanceId + "]: ", highLights);
			
			ProcessDiagramGenerator.setHighLights(highLights);
		}).fail(function(jqXHR, textStatus){
			console.log('Get HighLights['+processDefinitionId+'] failure: ', textStatus, jqXHR);
		});
		*/
    ActivitiRest.getHighLights(processInstanceId, this._drawHighLights);
  },
  _drawHighLights: function () {
    var highLights = this.highLights;
    ProcessDiagramGenerator.setHighLights(highLights);
  },

  // Load processDefinition

  drawDiagram: function (processDefinitionId) {
    // Hide all diagrams
    var diagrams = $('#' + this.options.diagramHolderId + ' div.diagram');
    diagrams.addClass('hidden');

    // If processDefinitionId doesn't contain ":" then it's a "processDefinitionKey", not an id.
    // Get process definition by key
    if (processDefinitionId.indexOf(':') < 0) {
      ActivitiRest.getProcessDefinitionByKey(processDefinitionId, this._drawDiagram);
    } else {
      this._drawDiagram.apply({ processDefinitionId: processDefinitionId });
    }
  },
  _drawDiagram: function () {
    var processDefinitionId = this.processDefinitionId;

    ProcessDiagramGenerator.addBreadCrumbsItem(processDefinitionId);

    // Check if processDefinition is already loaded and rendered

    var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);

    if (processDiagram != undefined && processDiagram != null) {
      //console.log("Process diagram " + processDefinitionId + " is already loaded");
      //return;

      var diagram = document.getElementById(processDefinitionId);
      $(diagram).removeClass('hidden');

      // Regenerate image
      var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
      ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);

      return;
    }

    //console.time('loadDiagram');

    // Load processDefinition

    ActivitiRest.getProcessDefinition(processDefinitionId, ProcessDiagramGenerator._generateDiagram);
  },
  _generateDiagram: function () {
    var processDefinitionDiagramLayout = this.processDefinitionDiagramLayout;

    //console.log("process-definition-diagram-layout["+processDefinitionDiagramLayout.processDefinition.id+"]: ", processDefinitionDiagramLayout);

    //console.timeEnd('loadDiagram');
    //console.time('generateDiagram');

    pb1.set('value', 0);
    ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
  },

  getProcessDefinitionByKey: function (processDefinitionKey) {
    var url = Lang.sub(this.options.processDefinitionByKeyUrl, { processDefinitionKey: processDefinitionKey });

    var processDefinition;
    $.ajax({
      url: url,
      type: 'POST',
      dataType: 'json',
      cache: false,
      async: false,
    })
      .done(function (data) {
        //console.log("ajax returned data");
        //console.log("ajax returned data:", data);
        processDefinition = data;
        if (!processDefinition) {
          //console.error("Process definition '" + processDefinitionKey + "' not found");
        }
      })
      .fail(function (jqXHR, textStatus) {
        //console.error('Get diagram layout['+processDefinitionKey+'] failure: ', textStatus, jqXHR);
      });

    if (processDefinition) {
      //console.log("Get process definition by key '" + processDefinitionKey + "': ", processDefinition.id);
      return processDefinition;
    } else {
      return null;
    }
  },

  addBreadCrumbsItem: function (processDefinitionId) {
    var TPL_UL_CONTAINER = '<ul></ul>',
      TPL_LI_CONTAINER = '<li id="{id}", processDefinitionId="{processDefinitionId}"><span>{name}</span></li>';

    if (!this.diagramBreadCrumbs) this.diagramBreadCrumbs = $('#' + this.options.diagramBreadCrumbsId);
    if (!this.diagramBreadCrumbs) return;

    var ul = this.diagramBreadCrumbs.find('ul');
    //console.log("ul: ", ul);
    if (ul.size() == 0) {
      ul = $(TPL_UL_CONTAINER);
      this.diagramBreadCrumbs.append(ul);
    }
    var liListOld = ul.find('li');
    //console.warn("liListOld", liListOld);

    // TODO: if there is any items after current then remove that before adding new item (m.b. it is a duplicate)
    var currentBreadCrumbsItemId = this.currentBreadCrumbsItemId;
    found = false;
    liListOld.each(function (index, item) {
      //console.warn("item:", $(this));
      if (!found && currentBreadCrumbsItemId == $(this).attr('id')) {
        found = true;
        return;
      }
      if (found) {
        //console.warn("remove ", $(this).attr("id"));
        $(this).remove();
      }
    });

    var liListNew = ul.find('li');

    //console.log("liListNew size: ", liListNew.size());
    var values = {
      id: 'breadCrumbsItem_' + liListNew.size(),
      processDefinitionId: processDefinitionId,
      name: processDefinitionId,
    };

    var tpl = Lang.sub(TPL_LI_CONTAINER, values);
    //console.log("tpl: ", tpl);
    ul.append(tpl);

    var li = ul.find('#' + values.id);
    //console.warn("li:", li);
    $('#' + values.id).on('click', this._breadCrumbsItemClick);

    ul.find('li').removeClass('selected');
    li.attr('num', liListNew.size());
    li.addClass('selected');
    this.currentBreadCrumbsItemId = li.attr('id');
  },
  _breadCrumbsItemClick: function () {
    var li = $(this),
      id = li.attr('id'),
      processDefinitionId = li.attr('processDefinitionId');
    //console.warn("_breadCrumbsItemClick: ", id, ", processDefinitionId: ", processDefinitionId);

    var ul = ProcessDiagramGenerator.diagramBreadCrumbs.one('ul');
    ul.find('li').removeClass('selected');
    li.addClass('selected');
    ProcessDiagramGenerator.currentBreadCrumbsItemId = li.attr('id');

    // Hide all diagrams
    var diagrams = $('#' + ProcessDiagramGenerator.options.diagramHolderId + ' div.diagram');
    diagrams.addClass('hidden');

    var processDiagram = ProcessDiagramGenerator.getProcessDiagram(processDefinitionId);

    var diagram = document.getElementById(processDefinitionId);
    if (!diagram) return;
    $(diagram).removeClass('hidden');

    // Regenerate image
    var processDefinitionDiagramLayout = processDiagram.processDefinitionDiagramLayout;
    ProcessDiagramGenerator.generateDiagram(processDefinitionDiagramLayout);
  },

  showFlowInfo: function (flow) {
    var diagramInfo = $('#' + this.options.diagramInfoId);
    if (!diagramInfo) return;

    var values = {
      flow: flow.flow,
      isDefault: flow.isDefault ? 'true' : '',
      isConditional: flow.isConditional ? 'true' : '',
      isHighLighted: flow.isHighLighted ? 'true' : '',
      sourceActivityId: flow.sourceActivityId,
      destinationActivityId: flow.destinationActivityId,
    };
    var TPL_FLOW_INFO =
      '<div>{flow}</div>' +
      '<div><b>sourceActivityId</b>: {sourceActivityId}</div>' +
      '<div><b>destinationActivityId</b>: {destinationActivityId}</div>' +
      '<div><b>isDefault</b>: {isDefault}</div>' +
      '<div><b>isConditional</b>: {isConditional}</div>' +
      '<div><b>isHighLighted</b>: {isHighLighted}</div>';
    var tpl = Lang.sub(TPL_FLOW_INFO, values);
    //console.log("info: ", tpl);
    diagramInfo.html(tpl);
  },

  showActivityInfo: function (activity) {
    var diagramInfo = $('#' + this.options.diagramInfoId);
    if (!diagramInfo) return;

    var values = {
      activityId: activity.getId(),
      name: activity.getProperty('name'),
      type: activity.getProperty('type'),
    };
    var TPL_ACTIVITY_INFO =
      '' + '<div><b>activityId</b>: {activityId}</div>' + '<div><b>name</b>: {name}</div>' + '<div><b>type</b>: {type}</div>';
    var TPL_CALLACTIVITY_INFO =
      '' + '<div><b>collapsed</b>: {collapsed}</div>' + '<div><b>processDefinitonKey</b>: {processDefinitonKey}</div>';

    var template = TPL_ACTIVITY_INFO;
    if (activity.getProperty('type') == 'callActivity') {
      values.collapsed = activity.getProperty('collapsed');
      values.processDefinitonKey = activity.getProperty('processDefinitonKey');
      template += TPL_CALLACTIVITY_INFO;
    } else if (activity.getProperty('type') == 'callActivity') {
    }

    var tpl = Lang.sub(template, values);
    //console.log("info: ", tpl);
    diagramInfo.html(tpl);
  },

  hideInfo: function () {
    var diagramInfo = $('#' + this.options.diagramInfoId);
    if (!diagramInfo) return;
    diagramInfo.html('');
  },

  vvoid: function () {},
};

var Lang = {
  SUBREGEX: /\{\s*([^\|\}]+?)\s*(?:\|([^\}]*))?\s*\}/g,
  UNDEFINED: 'undefined',
  isUndefined: function (o) {
    return typeof o === Lang.UNDEFINED;
  },
  sub: function (s, o) {
    return s.replace
      ? s.replace(Lang.SUBREGEX, function (match, key) {
          return !Lang.isUndefined(o[key]) ? o[key] : match;
        })
      : s;
  },
};

if (Lang.isUndefined(console)) {
  console = { log: function () {}, warn: function () {}, error: function () {} };
}
ProcessDiagramGenerator.init();
